import copy
import random
import sys
import numpy as np
import json


class GoBang_Agent():
    def __init__(self, n,sign,grid = None):
        self.grid = [[0 for j in range(n)] for i in range(n)]
        if grid != None:
            self.grid = grid
        self.n = len(grid)
        self.score = 0
        self.sign = sign
        self.win = 0  # 0 未知，1 赢 ，2 输
    def step(self,i,j):
        if self.win != 0:
            return
        self.score -= 1
        if self.grid[i][j] != 0:
            return
        self.grid[i][j] = self.sign
        if self.judge_row(i,j) or self.judge_col(i,j) or self.judge_lean(i,j):
            self.score += 100
            self.win = 1
    def judge_row(self,grid,i,j):
        a = i - 2
        count = 0
        while a <= i + 2:
            #print(a,i)
            if a < 0:
                a += 1
                continue
            if a >= self.n:
                break
            if grid[a][j] == self.sign:
                count += 1
            else:
                count = 0
            if count == 3:
                return True

            a += 1
        return False

    def judge_col(self,grid,i,j):
        a = j - 2
        count = 0
        while a <= j + 2:
            if a < 0:
                a += 1
                continue
            if a >= self.n:
                break
            if grid[i][a] == self.sign:
                count += 1
            else:
                count = 0
            if count == 3:
                return True
            a += 1
        return False
    def judge_lean(self,grid,i,j):
        return self.judge_lean_1(grid,i,j) or self.judge_lean_2(grid,i,j)
    def judge_lean_2(self,grid,i,j):
        a = i - 2
        b = j + 2
        count = 0
        while a <= i + 2:
            if a < 0 or b < 0:
                a += 1
                b -= 1
                continue
            if a >= self.n or b >= self.n:
                break
            if grid[a][b] == self.sign:
                count += 1
            else:
                count = 0
            if count == 3:
                return True
            a += 1
            b -= 1
        return False
    def judge_lean_1(self,grid,i,j):
        a = i - 2
        b = j - 2
        count = 0
        while a <= i + 2:
            if a < 0 or b < 0:
                a += 1
                b += 1
                continue
            if a >= self.n or b >= self.n:
                break
            if grid[a][b] == self.sign:
                count += 1
            else:
                count = 0
            if count == 3:
                return True
            a += 1
            b += 1
        return False


class GoBang_Train():
    def __init__(self, n, q=None):
        self.n = n
        self.grid = [[0 for j in range(n)] for i in range(n)]
        self.states = []
        self.player_1 = GoBang_Agent(n, 1, self.grid)
        self.res = []
        self.lr = 0.8
        self.r = dict()
        self.player_2 = GoBang_Agent(n, 2, self.grid)
        self.init_state()
        if q == None:
            self.q = dict()
            self.init_q()
        else:
            self.q = q

    def init_state(self):
        self.dfs([0, 1, 2], [])
        grid = copy.deepcopy(self.grid)
        for i in range(len(self.res)):
            for j in range(len(self.res)):
                for k in range(len(self.res)):
                    grid[0] = copy.copy(self.res[i])
                    grid[1] = copy.copy(self.res[j])
                    grid[2] = copy.copy(self.res[k])
                    self.states.append(copy.deepcopy(grid))
        cnt = 0
        states = copy.deepcopy(self.states)
        self.states = []
        for item in states:
            val = self.judge_total(item)

            item_temp = np.array(item)
            num_1 = np.sum(item_temp == 1)
            num_2 = np.sum(item_temp == 2)
            diff = num_1 - num_2

            if (diff == 0 or (diff == 1 and val == 100)) and val != -5:
                cnt += 1
                # self.q[f"{item}"] = defaultdict(int)
                self.states.append(item)
                self.r[f"{item}"] = val

    def init_q(self):
        for state in self.states:
            self.q[f"{state}"] = dict()
            # temp_state = copy.deepcopy(state)
            for i in range(self.n):
                for j in range(self.n):
                    if state[i][j] != 0:
                        continue
                    # temp_state[i][j] = 1
                    self.q[f"{state}"][f"{i},{j}"] = self.r[f"{state}"]
                    # temp_state[i][j] = 0

    def judge_total(self, grid):
        res = 0
        flag_1 = False
        flag_2 = False
        for i in range(self.n):
            for j in range(self.n):
                if grid[i][j] == 1:
                    if self.player_1.judge_row(grid, i, j) or self.player_1.judge_col(grid, i,
                                                                                      j) or self.player_1.judge_lean(
                            grid, i, j):
                        res = 100
                        if flag_2:
                            return -5
                        flag_1 = True
                if grid[i][j] == 2:
                    if self.player_2.judge_row(grid, i, j) or self.player_2.judge_col(grid, i,
                                                                                      j) or self.player_2.judge_lean(
                            grid, i, j):
                        res = -100
                        if flag_1:
                            return -5
                        flag_2 = True
        return res

    def dfs(self, nums, temp_list):
        if len(temp_list) > len(nums):
            return
        if len(temp_list) == len(nums):
            self.res.append(temp_list.copy())
        for i in range(len(nums)):
            temp_list.append(nums[i])
            self.dfs(nums, temp_list)
            temp_list.pop()

    def step_strict(self):
        next = "0,0"
        next_val = -1000.0
        for key in self.q[f"{self.grid}"].keys():
            if float(self.q[f"{self.grid}"][key]) > next_val:
                next_val = float(self.q[f"{self.grid}"][key])
                next = key
        next = next.split(",")

        return (int(next[0]), int(next[1]), 1)

    def step_soft(self):
        next = "0,0"

        res = []
        for key in self.q[f"{self.grid}"].keys():
            next_val = float(self.q[f"{self.grid}"][key])
            next = key
            next = next.split(",")
            res.append((next_val, int(next[0]), int(next[1]), 1))
        res = list(reversed(sorted(res, key=lambda x: x[0])))
        rand = random.randint(0, 2)

        return res[rand][1:]

    def printf(self):
        for i in range(self.n):
            print(self.grid[i])
        print("\n")

    def play(self):

        next = self.step_soft()

        self.grid[next[0]][next[1]] = next[2]
        self.printf()
        # print(game.q[f"{self.grid}"])
        for i in range(4):
            a = next[0]
            b = next[1]
            while self.grid[a][b] != 0:
                print("请输入您要下的棋的横坐标和纵坐标，以英文逗号分隔。如输入：0,1")
                try:
                    temp = list(map(int, sys.stdin.readline().strip().split(",")))
                except:
                    print("请按照指定格式进行输入！")
                    continue
                if len(temp) != 2:
                    print("请按照指定格式进行输入！")
                    continue
                a = temp[0]
                b = temp[1]

            self.grid[a][b] = 2
            self.printf()
            if (self.judge_total(self.grid) == -100):
                print("恭喜您获得胜利！")
                return
            next = self.step_strict()
            self.grid[next[0]][next[1]] = next[2]
            self.printf()
            if(self.judge_total(self.grid) == 100):
                print("您输了，请继续努力！")
                return
        print("哎哟不错哟，获得了平局！")
        return

    def train(self, epoch):
        for e in range(epoch):
            for state in self.states:
                if self.r[f"{state}"] in (-100, 100):
                    continue
                if random.random() < 0.2:
                    next_i = random.randint(0, self.n - 1)
                    next_j = random.randint(0, self.n - 1)
                else:
                    for a_i in range(self.n):
                        for a_j in range(self.n):
                            if f"{a_i},{a_j}" not in self.q[f"{state}"]:
                                continue

                            else:
                                next = "0,0"
                                next_val = -1000
                                key = f"{a_i},{a_j}"
                                if self.q[f"{state}"][key] > next_val:
                                    next_val = self.q[f"{state}"][key]
                                    next = key
                                next = next.split(",")
                                next_i = int(next[0])
                                next_j = int(next[1])

                if f"{next_i},{next_j}" not in self.q[f"{state}"]:
                    continue
                next_state = copy.deepcopy(state)

                next_state[next_i][next_j] = 1
                # print(next_state)
                temp_add_val = 1000
                cnt = 0
                if random.random() < 0.2:
                    b_i = random.randint(0, self.n - 1)
                    b_j = random.randint(0, self.n - 1)
                    if next_state[b_i][b_j] != 0:
                        continue
                    next_state[b_i][b_j] = 2
                    if f"{next_state}" not in self.q:
                        next_state[b_i][b_j] = 0
                        continue

                    next = "0,0"
                    next_val = -1000
                    for key in self.q[f"{next_state}"].keys():
                        if self.q[f"{next_state}"][key] > next_val:
                            next_val = self.q[f"{next_state}"][key]
                            next = key
                    next_max = next.split(",")
                    next_max_i = int(next_max[0])
                    next_max_j = int(next_max[1])
                    cnt += 1
                    temp_add_val = (self.r[f"{next_state}"] + 0.5 * self.q[f"{next_state}"][
                        f"{next_max_i},{next_max_j}"] - self.q[f"{state}"][f"{next_i},{next_j}"])
                else:
                    for b_i in range(self.n):
                        for b_j in range(self.n):
                            if next_state[b_i][b_j] != 0:
                                continue

                            next_state[b_i][b_j] = 2
                            if f"{next_state}" not in self.q:
                                next_state[b_i][b_j] = 0
                                continue

                            next = "0,0"
                            next_val = -1000
                            for key in self.q[f"{next_state}"].keys():
                                if self.q[f"{next_state}"][key] > next_val:
                                    next_val = self.q[f"{next_state}"][key]
                                    next = key
                            next_max = next.split(",")
                            next_max_i = int(next_max[0])
                            next_max_j = int(next_max[1])
                            cnt += 1

                            if temp_add_val == 1000:
                                temp_add_val = (self.r[f"{next_state}"] + 0.5 * self.q[f"{next_state}"][
                                    f"{next_max_i},{next_max_j}"] - self.q[f"{state}"][f"{next_i},{next_j}"])
                            else:
                                temp_add_val = min(temp_add_val, (
                                            self.r[f"{next_state}"] + 0.5 * self.q[f"{next_state}"][
                                        f"{next_max_i},{next_max_j}"] - self.q[f"{state}"][f"{next_i},{next_j}"]))
                            next_state[b_i][b_j] = 0

                if temp_add_val != 1000:
                    self.q[f"{state}"][f"{next_i},{next_j}"] += self.lr * (temp_add_val)


